package com.zzzzzz.app.account.controller;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.springframework.context.MessageSource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.support.SessionStatus;

import com.zzzzzz.app.account.NoSuchRoleException;
import com.zzzzzz.app.account.NoSuchUserException;
import com.zzzzzz.app.account.UserPasswordException;
import com.zzzzzz.app.account.model.bo.DayOptionBo;
import com.zzzzzz.app.account.model.bo.GenderOptionBo;
import com.zzzzzz.app.account.model.bo.MonthOptionBo;
import com.zzzzzz.app.account.model.bo.UserBo;
import com.zzzzzz.app.account.model.bo.YearOptionBo;
import com.zzzzzz.app.account.model.po.Role;
import com.zzzzzz.app.account.model.po.User;
import com.zzzzzz.app.account.model.vo.EditPasswordVo;
import com.zzzzzz.app.account.model.vo.EditProfileBasicVo;
import com.zzzzzz.app.account.model.vo.EditProfileContactVo;
import com.zzzzzz.app.account.model.vo.EditUserPortraitVo;
import com.zzzzzz.app.account.model.vo.ForgotPasswordVo;
import com.zzzzzz.app.account.model.vo.RegisterVo;
import com.zzzzzz.app.account.model.vo.ResetPasswordVo;
import com.zzzzzz.app.account.model.vo.RoleVo;
import com.zzzzzz.app.account.service.RoleManager;
import com.zzzzzz.app.account.service.UserManager;
import com.zzzzzz.app.account.service.UserRoleManager;
import com.zzzzzz.common.util.Constants;
import com.zzzzzz.common.util.Validator;
import com.zzzzzz.core.controller.BaseController;
import com.zzzzzz.core.dao.search.SearchContainer;
import com.zzzzzz.core.exception.SystemException;
import com.zzzzzz.core.model.po.Ticket;
import com.zzzzzz.core.service.TicketManager;
import com.zzzzzz.core.service.UploadService;
import com.zzzzzz.core.servlet.SessionMessages;
import com.zzzzzz.core.validator.FileUploadValidator;

@Controller
public class UserController extends BaseController {
	
	@Resource
	private UserManager userManager;

	@Resource
	private RoleManager roleManager;

	@Resource
	private UserRoleManager userRoleManager;
	
	@Resource
	private TicketManager ticketManager;
	
	@Resource
	private UploadService uploadService;
	
	@Resource
	private MessageSource messageSource;
	
	private FileUploadValidator fileUploadValidator = new FileUploadValidator();

	private List<GenderOptionBo> genderOptions;
	
	private List<YearOptionBo> yearOptions;
	
	private List<MonthOptionBo> monthOptions;
	
	private List<DayOptionBo> dayOptions;
	
	@RequiresGuest
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String viewRegisterPage(Model model, @ModelAttribute RegisterVo registerVo) {
		model.addAttribute("cur", "register");
		return "account/register";
	}
	
	@RequiresGuest
	@RequestMapping(value = "/register", method = RequestMethod.POST)
	public String processRegister(Model model, RegisterVo registerVo, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject( "message.error.process.request");
			return viewRegisterPage(model, registerVo);
		}
		try {
			User user = new User();
			registerVo.updatePo(user);
			
			userManager.addAndSendAccountActivationEmail(user, request);
			model.addAttribute("email", registerVo.getEmail());
			return "account/register_success";
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value = "/user/is_email_existing", method = RequestMethod.POST)
	public @ResponseBody String isEmailExisting(@RequestParam(value="email", required=true) String email, 
				Model model, HttpServletRequest request) throws Exception {
		boolean isEmailExisting = false;
		String result = "";
		try{
			isEmailExisting = userManager.isEmailExisting(email);
		}catch(Exception e) {
			if(e instanceof NoSuchUserException) {
				isEmailExisting = false;
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		if(isEmailExisting) {
			result = "TRUE";
		}else {
			result = "FALSE";
		}
		return result;
	}
	
	@RequestMapping(value = "/terms_of_service", method = RequestMethod.GET)
	public String viewTermsOfServicePage(Model model,@ModelAttribute RegisterVo registerVo) {
		return "account/terms_of_service";
	}
	
	@RequiresGuest
	@RequestMapping(value = "/user/forgot_password", method = RequestMethod.GET)
	public String viewForgotPasswordPage(Model model, @ModelAttribute ForgotPasswordVo forgotPasswordVo) {
		return "account/user/forgot_password";
	}
	
	@RequiresGuest
	@RequestMapping(value = "/user/forgot_password", method = RequestMethod.POST)
	public String processForgotPassword(Model model, ForgotPasswordVo forgotPasswordVo, BindingResult result, SessionStatus sessionStatus, 
			HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
    		result.reject("message.error.process.request");
			return viewForgotPasswordPage(model, forgotPasswordVo);
		}

		String email = forgotPasswordVo.getEmail();
		
		try{
			// send reset password email
			userManager.sendResetPasswordEmail(email, request);
			SessionMessages.add(request, "msg_success_pwd_send_reset_link", messageSource.getMessage("message.success.password.send.reset.link", new Object[]{}, Locale.ROOT));
	        return "redirect:/login";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequiresUser
	@RequestMapping(value="/dashboard", method=RequestMethod.GET)
	public String viewDashboardPage(Model model, HttpServletRequest request) throws Exception {
		if(isSignedIn()) {
			model.addAttribute("cur", "dashboard");
			try {
				User currentUser = userManager.findCurrentUser();
				UserBo currentUserBo = new UserBo(currentUser);
				model.addAttribute("currentUserBo", currentUserBo);
				return "account/user/dashboard";
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
	    		throw e; // handle by Spring
			}
		}else {
	        return "redirect:/login";
		}
	}

	@RequiresUser
	@RequestMapping(value="/account_settings", method=RequestMethod.GET)
	public String viewAccountSettingsPage(Model model, @RequestParam(value="tab", required=false) String tab) {
		model.addAttribute("tab", tab);
		return "account/user/account_settings";
	}

	@RequestMapping(value = "/user/is_password_correct", method = RequestMethod.POST)
	public @ResponseBody String isPasswordCorrect(@RequestParam(value="cur_password", required=true) String curPassword, 
				Model model, HttpServletRequest request) {
		String result = "";
		User user = null;
		try{
			user = userManager.findCurrentUser();
		}catch(Exception e) {
			if(e instanceof NoSuchUserException) {
				logger.error(e.getMessage());
			}else {
				logger.error(e.getMessage(), e);
			}
		}
		if(user != null) {
			result = user.getPassword().equals(new Sha256Hash(curPassword).toHex()) ? "OK": "INCORRECT";
		}
		return result;
	}

	@RequestMapping(value="/account_settings", method=RequestMethod.POST)
	public @ResponseBody String processAccountSettings(Model model, HttpServletRequest request, 
			@RequestParam(value="action", required=true) String action) {
		String result = "";
		if(isSignedIn()) {
			try {
				User user = userManager.findCurrentUser();
				if("edit_password".equalsIgnoreCase(action)) {
					String currentPassword = user.getPassword();
					String newPassword = request.getParameter("new_password");
					userManager.editPasswordByUserId(user.getUserId(), currentPassword, new Sha256Hash(newPassword).toHex());
				}
				
				result = "SUCCESS";
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
			}
		}else {
			result = "AUTHENTICATE";
		}
		return result;
	}

	@RequiresUser
	@RequestMapping(value="/profile_edit", method=RequestMethod.GET)
	public String viewEditProfilePage(Model model, @ModelAttribute EditProfileBasicVo editProfileBasicVo, 
			@ModelAttribute EditProfileContactVo editProfileContactVo, HttpServletRequest request, 
			@RequestParam(value="tab", required=false) String tab) throws Exception {
			model.addAttribute("tab", tab);
		try{
			User user = userManager.findCurrentUser();
			if(user != null) {
				if(Validator.isNull(tab) || "basic".equalsIgnoreCase(tab)) {
					// for basic
					editProfileBasicVo.setFirstName(user.getFirstName());
					editProfileBasicVo.setLastName(user.getLastName());
					editProfileBasicVo.setUsername(user.getUsername());
					editProfileBasicVo.setAboutMe(user.getAboutMe());
					editProfileBasicVo.setGender(user.getGender());
					
					Date dob = user.getDob();
					if(dob != null) {
						Calendar c = Calendar.getInstance();
						c.setTime(dob);
						String day = String.valueOf(c.get(Calendar.DAY_OF_MONTH));
						String month = String.valueOf(c.get(Calendar.MONTH) + 1);
						String year = String.valueOf(c.get(Calendar.YEAR));
						
						editProfileBasicVo.setDay(day);
						editProfileBasicVo.setMonth(month);
						editProfileBasicVo.setYear(year);
					}
					
					model.addAttribute("genderOptions", getGenderOptions());
					model.addAttribute("dayOptions", getDayOptions());
					model.addAttribute("monthOptions", getMonthOptions());
					model.addAttribute("yearOptions", getYearOptions());
				}else if("photo".equalsIgnoreCase(tab)) {
					// for photo
					
					
				}else if("contact".equalsIgnoreCase(tab)) {
					// for contact
					editProfileContactVo.setEmail(user.getEmail());
					editProfileContactVo.setWebsite(user.getWebsite());
				}
			}
			return "account/user/profile_edit";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
//	@RequestMapping(value = "/user/is_dob_correct", method = RequestMethod.POST)
//	public @ResponseBody String isDobCorrect(@RequestParam(value="day", required=true) String day, 
//				@RequestParam(value="month", required=true) String month, 
//				@RequestParam(value="year", required=true) String year, 
//				Model model, HttpServletRequest request) throws Exception {
//		boolean isDobValid = false;
//		boolean isDobEligible = false;
//		try{
//			isDobValid = isDobValid(year, month, day);
//			isDobEligible = isDobEligible(year, month, day);
//		}catch(Exception e) {
//			logger.error(e.getMessage(), e);
//		}
//		String result = "";
//		if(isDobValid && isDobEligible) {
//			result = "OK";
//		}else if(!isDobValid) {
//			result = "INVALID";
//		}else if(!isDobEligible) {
//			result = "INELIGIBLE";
//		}
//
//		return result;
//	}

	@RequiresUser
	@RequestMapping(value="/profile_edit", method=RequestMethod.POST)
	public String processEditProfile(Model model, EditProfileBasicVo editProfileBasicVo, EditProfileContactVo editProfileContactVo, BindingResult result,
			@RequestParam(value="tab", required=true) String tab, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			return viewEditProfilePage(model, editProfileBasicVo, editProfileContactVo, request, tab);
		}
		User user = null;
		try{
			user = userManager.findCurrentUser();
			if(user != null) {
				if("basic".equalsIgnoreCase(tab)) {
					editProfileBasicVo.updatePo(user);
					userManager.editProfileBasic(user);
					SessionMessages.add(request, "msg_success_update_pe_basic", messageSource.getMessage("message.success.process.request", new Object[]{}, Locale.ROOT));
				}else if("contact".equalsIgnoreCase(tab)) {
					editProfileContactVo.updatePo(user);
					userManager.editProfileContact(user);
					SessionMessages.add(request, "msg_success_update_pe_contact", messageSource.getMessage("message.success.process.request", new Object[]{}, Locale.ROOT));
				}
			}
			return viewEditProfilePage(model, editProfileBasicVo, editProfileContactVo, request, tab);
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}

	@RequiresUser
	@RequestMapping(value="/profile", method=RequestMethod.GET)
	public String viewProfilePage(Model model, @RequestParam(value="id", required=false) String id, HttpServletRequest request) throws Exception {
		model.addAttribute("cur", "profile");
		try{
			User profileOwner = null;
			if(Validator.isNull(id)) {
				profileOwner = userManager.findCurrentUser();
			}else {
				long userId = Long.valueOf(id);
				profileOwner = userManager.findById(userId);
			}
			UserBo profileOwnerBo = new UserBo(profileOwner);
			model.addAttribute("profileOwnerBo", profileOwnerBo);
		}catch(Exception e) {
			if(e instanceof NumberFormatException) {
				return "redirect:/dashboard";
			}else if(e instanceof NoSuchUserException) {
				SessionMessages.add(request, "msg_error_profile_not_found", messageSource.getMessage("message.error.profile.not.found", new Object[]{}, Locale.ROOT));
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		return "account/user/profile";
	}

	@RequestMapping(value="/user/reset_password/{token}", method=RequestMethod.GET)
	public String viewResetPasswordPage(Model model, @ModelAttribute ResetPasswordVo resetPasswordVo, 
			@PathVariable String token, HttpServletRequest request) {
		if(isSignedIn()) {
			SessionMessages.add(request, "msg_info_already_signed_in", messageSource.getMessage("message.info.already.signed.in", new Object[]{}, Locale.ROOT));
        	return "redirect:/dashboard";
		}else {
			model.addAttribute("token", token);
			return "account/user/reset_password";
		}
	}
	
    @RequestMapping(value="/user/reset_password/{token}", method= RequestMethod.POST)
    public String processResetPassword(Model model, @ModelAttribute ResetPasswordVo resetPasswordVo, BindingResult result, 
    		@PathVariable String token, HttpServletRequest request) throws Exception {
        if(result.hasErrors()) {
    		result.reject("message.error.process.request");
            return viewResetPasswordPage(model, resetPasswordVo, token, request);
        }

        try {
			Ticket ticket = getTicket(token, Constants.TICKET_CATEGORY.PASSWORD_RESET);
			
			if(ticket == null) {
	    		result.reject("message.error.reset.password.token.invalid");
	            return viewResetPasswordPage(model, resetPasswordVo, token, request);
			}else if(ticket.isExpired()) {
				SessionMessages.add(request, "msg_alert_pwd_reset_token_expired", messageSource.getMessage("message.alert.password.reset.token.expired", new Object[]{}, Locale.ROOT));
	            return viewResetPasswordPage(model, resetPasswordVo, token, request);
			}
			
	        long userId = ticket.getClassPK();
			userManager.resetPassword(userId, resetPasswordVo.getNewPassword(), resetPasswordVo.getReNewPassword());
			ticketManager.delete(token);
	        
	        // login
			// userManager.login(user.getEmail(), user.getPassword(), false);
			
			SessionMessages.add(request, "msg_success_pwd_reset", messageSource.getMessage("message.success.password.reset", new Object[]{}, Locale.ROOT));
			
    		// return "redirect:/dashboard";

    		return "redirect:/login";
	    	
		} catch (Exception e) {
			if(e instanceof UserPasswordException) {
				UserPasswordException upe = (UserPasswordException) e;
				if(upe.getType() == UserPasswordException.PASSWORD_INVALID) {
		    		result.reject("message.error.reset.password.invalid");
				}
				if(upe.getType() == UserPasswordException.PASSWORDS_DO_NOT_MATCH) {
		    		result.reject("message.error.reset.password.not.match");
				}
				if(upe.getType() == UserPasswordException.PASSWORD_SAME_AS_CURRENT) {
		    		result.reject("message.error.reset.password.same.as.current");
				}
			}else {
	    		logger.error(e.getMessage(), e);
	    		throw e; // handle by Spring
			}
		}
        return viewResetPasswordPage(model, resetPasswordVo, token, request);
    }
    
	@RequestMapping(value="/user/resend_activation_email/{token}", method=RequestMethod.GET)
	public String processResendActivationEmail(Model model, @PathVariable String token, HttpServletRequest request) {
		try{
			Ticket ticket = getTicket(token, Constants.TICKET_CATEGORY.ACCOUNT_ACTIVATION);
			if(ticket != null) {
				if(ticket.isExpired()) {
					// token expired, resend activation email and redirect to login page
					long userId = ticket.getClassPK();
					userManager.sendAccountActivationEmail(userId, request);
					ticketManager.delete(token);
					SessionMessages.add(request, "msg_success_resend_activation_email", messageSource.getMessage("message.success.resend.activation.email", new Object[]{}, Locale.ROOT));
				}
			}
			return "redirect:/login";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			return "account/error";
		}
	}
    
	@RequestMapping(value = "/user/activate/{token}", method = RequestMethod.GET)
	public String activate(Model model, @PathVariable String token, HttpServletRequest request) throws Exception {
		if(isSignedIn()) {
			SessionMessages.add(request, "msg_info_already_signed_in", messageSource.getMessage("message.info.already.signed.in", new Object[]{}, Locale.ROOT));
        	return "redirect:/dashboard";
		}else {
			try{
				Ticket ticket = getTicket(token, Constants.TICKET_CATEGORY.ACCOUNT_ACTIVATION);
				if(ticket != null) {
					if(ticket.isExpired()) {
						// token expired, guide to regain activation email
						String resendActivationEmailURL = request.getContextPath() + "/user/resend_activation_email/" + token;
						SessionMessages.add(request, "msg_alert_account_activate_token_expired", messageSource.getMessage("message.alert.account.activate.token.expired", new Object[]{resendActivationEmailURL}, Locale.ROOT));
					}else {
						// token not expired, activate account and redirect to login page
				        long userId = ticket.getClassPK();
						User user = userManager.activate(userId);
						ticketManager.delete(token);
				        
				        // login
						// userManager.login(user.getEmail(), user.getPassword(), false);
						
						SessionMessages.add(request, "msg_success_activate_account", messageSource.getMessage("message.success.activate.account", new Object[]{}, Locale.ROOT));
						return "redirect:/login";
						// return "redirect:/dashboard";
					}
				}else {
					// token invalid
					SessionMessages.add(request, "msg_error_account_activate_token_invalid", messageSource.getMessage("message.error.account.activate.token.invalid", new Object[]{}, Locale.ROOT));
				}
				return "account/register_guide";
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
	}

	@RequestMapping(value = "/control_panel", method = RequestMethod.GET)
	public String viewControlPanelPage(Model model, HttpServletRequest request) {
		model.addAttribute("cur", "control_panel");
		return "admin/control_panel";
	}

	public String viewUsersPage(Model model, HttpServletRequest request) {
		model.addAttribute("tab1", "users");
		return viewControlPanelPage(model, request);
	}

	public String viewRolesPage(Model model, HttpServletRequest request) {
		model.addAttribute("tab1", "roles");
		return viewControlPanelPage(model, request);
	}
	
	@RequestMapping(value = "/control_panel/users", method = RequestMethod.GET)
	public String viewUserListPage(Model model, HttpServletRequest request) throws Exception {
		return viewUserListPage(model, 1, request);
	}
	
	@RequestMapping(value = "/control_panel/users/page/{cur}", method = RequestMethod.GET)
	public String viewUserListPage(Model model, @PathVariable Integer cur, HttpServletRequest request) throws Exception {
		try{
			model.addAttribute("searchContainer", populateUserSearchContainer(request, cur));
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
		return viewUsersPage(model, request);
	}
	
	private SearchContainer populateUserSearchContainer(HttpServletRequest request, int cur) throws SystemException {
		String contextPath = request.getContextPath();
		String actionPrefixURL = contextPath + "/control_panel/users/page/";
		return userManager.findUserList(cur, actionPrefixURL);
	}
	
	@RequestMapping(value = "/control_panel/roles", method = RequestMethod.GET)
	public String viewRoleListPage(Model model, HttpServletRequest request) throws Exception {
		return viewRoleListPage(model, 1, request);
	}
	
	@RequestMapping(value = "/control_panel/roles/page/{cur}", method = RequestMethod.GET)
	public String viewRoleListPage(Model model, @PathVariable Integer cur, HttpServletRequest request) throws Exception {
		try{
			model.addAttribute("searchContainer", populateRoleSearchContainer(request, cur));
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
		return viewRolesPage(model, request);
	}
	
	private SearchContainer populateRoleSearchContainer(HttpServletRequest request, int cur) throws SystemException {
		String contextPath = request.getContextPath();
		String actionPrefixURL = contextPath + "/control_panel/roles/page/";
		return roleManager.findRoleList(cur, actionPrefixURL);
	}
	
	@RequestMapping(value = "/control_panel/role/add", method = RequestMethod.GET)
	public String viewAddRolePage(Model model, HttpServletRequest request, @ModelAttribute RoleVo roleVo) {
		model.addAttribute("tab2", "add_or_edit");
		model.addAttribute("cmd", "add");
		return viewRolesPage(model, request);
	}
	
	@RequestMapping(value = "/control_panel/role/delete", method = RequestMethod.GET)
	public String deleteRole(Model model, HttpServletRequest request, 
			@RequestParam(value="id", required=true) Long roleId) throws Exception {
		try{
			roleManager.delete(roleId);
		}catch(Exception e) {
    		logger.error(e.getMessage(), e);
    		throw e; // handle by Spring
		}
		return "redirect:/control_panel/roles";
	}
	
	@RequestMapping(value = "/control_panel/roles/delete", method = RequestMethod.POST)
	public String deleteRoles(Model model, HttpServletRequest request) throws Exception {
		try{
			String[] roleIds = request.getParameterValues("select_case");
			if(roleIds != null) {
				for(int i=0; i<roleIds.length; i++) {
					String roleId_ = roleIds[i];
					long roleId = Long.valueOf(roleId_);
					roleManager.delete(roleId);
				}
				SessionMessages.add(request, "msg_success_delete_roles", messageSource.getMessage("message.success.process.request", new Object[]{}, Locale.ROOT));
			}else {
				SessionMessages.add(request, "msg_info_delete_roles_no_item_selected", messageSource.getMessage("message.info.no.item.selected", new Object[]{}, Locale.ROOT));
			}
			return "redirect:/control_panel/roles";
		}catch(Exception e) {
    		logger.error(e.getMessage(), e);
    		throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value = "/control_panel/role/{id}/current_members", method = RequestMethod.GET)
	public String viewCurrentRoleMembers(Model model, HttpServletRequest request, @PathVariable Long id) throws Exception {
		return viewCurrentRoleMembers(model, request, id, 1);
	}
	
	@RequestMapping(value = "/control_panel/role/{id}/current_members/page/{cur}", method = RequestMethod.GET)
	public String viewCurrentRoleMembers(Model model, HttpServletRequest request, @PathVariable Long id, @PathVariable Integer cur) throws Exception {
		try{
			model.addAttribute("tab2", "assign_members");
			model.addAttribute("tab3", "current");
			model.addAttribute("roleId", id);
			model.addAttribute("searchContainer", populateCurrentRoleMembersSearchContainer(request, id, cur));
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
		return viewRolesPage(model, request);
	}
	
	private SearchContainer populateCurrentRoleMembersSearchContainer(HttpServletRequest request, long roleId, int cur) throws SystemException {
		String contextPath = request.getContextPath();
		String actionPrefixURL = contextPath + "/control_panel/role/" + roleId + "/current_members/page/";
		return userRoleManager.findCurrentRoleMembers(roleId, false, cur, actionPrefixURL);
	}
	
	@RequestMapping(value = "/control_panel/role/{id}/available_members", method = RequestMethod.GET)
	public String viewAvailableRoleMembers(Model model, HttpServletRequest request, @PathVariable Long id) throws Exception {
		return viewAvailableRoleMembers(model, request, id, 1);
	}
	
	@RequestMapping(value = "/control_panel/role/{id}/available_members/page/{cur}", method = RequestMethod.GET)
	public String viewAvailableRoleMembers(Model model, HttpServletRequest request, @PathVariable Long id, @PathVariable Integer cur) throws Exception {
		try{
			model.addAttribute("tab2", "assign_members");
			model.addAttribute("tab3", "available");
			model.addAttribute("roleId", id);
			model.addAttribute("searchContainer", populateAvailableRoleMembersSearchContainer(request, id, cur));
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
		return viewRolesPage(model, request);
	}
	
	private SearchContainer populateAvailableRoleMembersSearchContainer(HttpServletRequest request, long roleId, int cur) throws SystemException {
		String contextPath = request.getContextPath();
		String actionPrefixURL = contextPath + "/control_panel/role/" + roleId + "/available_members/page/";
		return userRoleManager.findAvailableRoleMembers(roleId, false, cur, actionPrefixURL);
	}
	
	@RequestMapping(value = "/control_panel/role/{id}/current_members/remove", method = RequestMethod.POST)
	public String removeRoleMembers(Model model, HttpServletRequest request, @PathVariable Long id) throws Exception {
		try{
			String[] userIds = request.getParameterValues("select_case");
			if(userIds != null) {
				for(int i=0; i<userIds.length; i++) {
					userRoleManager.removeAssociation(Long.valueOf(userIds[i]), id);
				}
				SessionMessages.add(request, "msg_success_remove_user_role_association", messageSource.getMessage("message.success.process.request", new Object[]{}, Locale.ROOT));
			}else {
				SessionMessages.add(request, "msg_info_remove_user_role_association_no_item_selected", messageSource.getMessage("message.info.no.item.selected", new Object[]{}, Locale.ROOT));
			}
		}catch(Exception e) {
    		logger.error(e.getMessage(), e);
    		throw e; // handle by Spring
		}
		return "redirect:/control_panel/role/" + id + "/current_members";
	}
	
	@RequestMapping(value = "/control_panel/role/{id}/available_members/add", method = RequestMethod.POST)
	public String addRoleMembers(Model model, HttpServletRequest request, @PathVariable Long id) throws Exception {
		try{
			String[] userIds = request.getParameterValues("select_case");
			if(userIds != null) {
				for(int i=0; i<userIds.length; i++) {
					userRoleManager.addAssociation(Long.valueOf(userIds[i]), id);
					SessionMessages.add(request, "msg_success_add_user_role_association", messageSource.getMessage("message.success.process.request", new Object[]{}, Locale.ROOT));
				}
			}else {
				SessionMessages.add(request, "msg_info_add_user_role_association_no_item_selected", messageSource.getMessage("message.info.no.item.selected", new Object[]{}, Locale.ROOT));
			}
		}catch(Exception e) {
    		logger.error(e.getMessage(), e);
    		throw e; // handle by Spring
		}
		return "redirect:/control_panel/role/" + id + "/available_members";
	}
	
	@RequestMapping(value = "/control_panel/role/{id}", method = RequestMethod.GET)
	public String viewEditRolePage(Model model, HttpServletRequest request, @ModelAttribute RoleVo roleVo, 
			@PathVariable Long id) throws Exception {
		try{
			model.addAttribute("tab2", "add_or_edit");
			model.addAttribute("cmd", "edit");
			Role role = roleManager.findById(id);
			roleVo.setRoleId(role.getRoleId());
			roleVo.setName(role.getName());
			roleVo.setDescription(role.getDescription());
		} catch (Exception e) {
			if(e instanceof NoSuchRoleException) {
				SessionMessages.add(request, "msg_error_role_not_found", messageSource.getMessage("message.error.role.not.found", new Object[]{}, Locale.ROOT));
				logger.error(e.getMessage());
			}else {
	    		logger.error(e.getMessage(), e);
	    		throw e; // handle by Spring
			}
		}
		return viewRolesPage(model, request);
	}

	@RequestMapping(value = "/control_panel/roles/add_or_edit", method = RequestMethod.POST)
	public String processAddOrEditRole(Model model, HttpServletRequest request, @ModelAttribute RoleVo roleVo, BindingResult result) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			if(Validator.isNull(roleVo.getRoleId())) {
				return viewAddRolePage(model, request, roleVo);
			}else {
				return viewEditRolePage(model, request, roleVo, roleVo.getRoleId());
			}
		}
		try{
			Role role = null;
			if(Validator.isNull(roleVo.getRoleId())) {
				// add role
				role = new Role();
				roleVo.updatePo(role);
				roleManager.add(role);
			}else {
				// edit role
				role = roleManager.findById(roleVo.getRoleId());
				roleVo.updatePo(role);
				roleManager.edit(role);
			}
			
			SessionMessages.add(request, "msg_success_edit_role", messageSource.getMessage("message.success.process.request", new Object[]{}, Locale.ROOT));
			return viewRoleListPage(model, request);
		} catch (Exception e) {
			if(e instanceof DuplicateKeyException) {
				SessionMessages.add(request, "msg_error_duplicate_entry_exists", messageSource.getMessage("message.error.duplicate.entry.exists", new Object[]{roleVo.getName(), "name"}, Locale.ROOT));
				return viewRoleListPage(model, request);
			}else {
				logger.error(e.getMessage(), e);
				throw e;
			}
		}
	}

	@RequestMapping(value = "/control_panel/settings", method = RequestMethod.GET)
	public String viewSettingPage(Model model, HttpServletRequest request) {
		model.addAttribute("tab1", "settings");
		return viewControlPanelPage(model, request);
	}
	
	@RequestMapping(value = "/help", method = RequestMethod.GET)
	public String viewHelpPage(Model model, HttpServletRequest request) {
		return "/help";
	}
	
	
	
	
	
	
	
	


	@RequestMapping(value="/account/editpic", method = RequestMethod.GET)
	public String editUserPortraitView(Model model, @ModelAttribute EditUserPortraitVo editUserPortraitVo) throws Exception {
		Long userId = userManager.findCurrentUserId();
		User user = userManager.findById(userId);
		editUserPortraitVo.fromPo(user);
		return "account/user/editpic";
	}
	
	@RequestMapping(value = "/account/editpic", method = RequestMethod.POST)
	public String editPic(Model model,EditUserPortraitVo editUserPortraitVo,BindingResult errors,HttpServletRequest request) throws Exception {
		fileUploadValidator.validate(editUserPortraitVo.getFileData(), errors);
		if(errors.hasErrors()){
			return editUserPortraitView(model, editUserPortraitVo);
		}
		
		String portrait = uploadService.saveToMongoImage(editUserPortraitVo.getFileData());
		Long userId = userManager.findCurrentUserId();
		User user = userManager.findById(userId);
		userManager.editPortraitByUserId(user.getUserId(), portrait);
		
		return "redirect:/account/edit";
	}
	
	@RequestMapping(value="/account/editpassword", method = RequestMethod.GET)
	public String editPasswordView(Model model,@ModelAttribute EditPasswordVo editPasswordVo) throws Exception {
		return "account/user/editpassword";
	}
	
	@RequestMapping(value = "/account/editpassword", method = RequestMethod.POST)
	public String editPassword(Model model, EditPasswordVo editPasswordVo,BindingResult errors) throws Exception {
		Long userId = userManager.findCurrentUserId();
		User user = userManager.findById(userId);
		if(!user.getPassword().equals(editPasswordVo.getCurrentPassword())){
			errors.rejectValue("oldPassword", "error.password.error");
		}
		if(errors.hasErrors()){
			return editPasswordView(model, editPasswordVo);
		}
		userManager.editPasswordByUserId(user.getUserId(), editPasswordVo.getCurrentPassword(), editPasswordVo.getNewPassword());
		return "redirect:/account/edit";
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	public List<GenderOptionBo> getGenderOptions() {
		if(genderOptions == null) {
			genderOptions = new ArrayList<GenderOptionBo>();
			for(int i=0; i<genderArray.length; i++) {
				String genderLabel = genderArray[i];
				GenderOptionBo gender = new GenderOptionBo(genderLabel, genderLabel.toLowerCase());
				genderOptions.add(gender);
			}
		}
		return genderOptions;
	}

	public List<YearOptionBo> getYearOptions() {
        Calendar calendar = Calendar.getInstance();
        int curYear = calendar.get(Calendar.YEAR);
		if(yearOptions == null) {
			yearOptions = new ArrayList<YearOptionBo>();
	        for(int i=curYear; i>=curYear-106; i--) {
				String yearLabel = i + "";
				YearOptionBo yearOption = new YearOptionBo(yearLabel, yearLabel);
				yearOptions.add(yearOption);
	        }
		}
		return yearOptions;
	}

	public List<MonthOptionBo> getMonthOptions() {
		if(monthOptions == null) {
			monthOptions = new ArrayList<MonthOptionBo>();
			for(int i=1; i<=monthArray.length; i++) {
				String monthLabel = monthArray[i-1];
				String monthValue = i + "";
				MonthOptionBo monthOption = new MonthOptionBo(monthLabel, monthValue);
				monthOptions.add(monthOption);
			}
		}
		return monthOptions;
	}

	public List<DayOptionBo> getDayOptions() {
		if(dayOptions == null) {
			dayOptions = new ArrayList<DayOptionBo>();
			for(int i=1; i<=31; i++) {
				String dayLabel = i + "";
				DayOptionBo dayOption = new DayOptionBo(dayLabel, dayLabel);
				dayOptions.add(dayOption);
			}
		}
		return dayOptions;
	}
	
	public boolean isDobValid(String year_str, String month_str, String day_str) {
		boolean isCorrect = false;
		int year = Integer.valueOf(year_str);
		int month = Integer.valueOf(month_str);
		int day = Integer.valueOf(day_str);
		Calendar c = Calendar.getInstance();
		c.set(Calendar.YEAR, year);
		c.set(Calendar.MONTH, month-1);
		
		int maximumDays = c.getActualMaximum(Calendar.DAY_OF_MONTH);
		if(day <= maximumDays) {
			isCorrect = true;
		}
		return isCorrect;
	}
	
	public boolean isDobEligible(String year_str, String month_str, String day_str) {
		boolean isCorrect = false;
		Date now = new Date();
		String dob_str = year_str + "-" + month_str + "-" + day_str;
		try {
			Date dob = format.parse(dob_str);
			if(dob.getTime() < now.getTime()) {
				isCorrect = true;
			}
		} catch (ParseException e) {
			logger.error(e.getMessage(), e);
		}
		return isCorrect;
	}
	
	private Ticket getTicket(String token, String category) {
		if (Validator.isNull(token)) {
			return null;
		}
		try {
			Ticket ticket = ticketManager.findByKeyAndCategory(token, category);
			return ticket;
			
		}catch (Exception e) {
			// do nothing
		}
		return null;
	}
	
//	private Ticket getTicket(String token) {
//
//		if (Validator.isNull(token)) {
//			return null;
//		}
//
//		try {
//			Ticket ticket = ticketManager.findByKey(token);
//			boolean isExpired = false;
//			Date expirationDate = ticket.getExpirationDate();
//			
//			if ((expirationDate != null) && (expirationDate.before(new Date()))) {
//				isExpired = true;
//			}
//			if (!isExpired) {
//				return ticket;
//			}
//			else {
//				ticketManager.delete(token);
//			}
//		}
//		catch (Exception e) {
//		}
//
//		return null;
//	}
	

	private static final String[] genderArray = {"Male", "Female"};

	private static final String[] monthArray = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};

	private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
	
	private static Logger logger = Logger.getLogger(UserController.class);
}
